package compiler.scanner;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import compiler.scanner.Token;

public class Scanner {
	private static final Map<String, Token> reservedIdents = new HashMap<String, Token>();
	static {
		reservedIdents.put("if", Token.If);
		reservedIdents.put("then", Token.Then);
		reservedIdents.put("else", Token.Else);
		reservedIdents.put("end", Token.End);
		reservedIdents.put("while", Token.While);
		reservedIdents.put("do", Token.Do);
		reservedIdents.put("return", Token.Return);
		reservedIdents.put("var", Token.Var);
		reservedIdents.put("integer", Token.Integer);
		reservedIdents.put("array", Token.Array);
		reservedIdents.put("of", Token.Of);
		reservedIdents.put("procedure", Token.FuncProc);
		reservedIdents.put("function", Token.FuncProc);
		reservedIdents.put("begin", Token.Begin);
		reservedIdents.put("module", Token.Module);
	};
		
	private final InputStream input;

	// look ahead token
	private Token lookAhead;
	
	// the character we are currently processing
	private char character;
	private int line = 1;
	private int position = 0;

	public Scanner(InputStream in) throws IOException {
		this.input = in;
		read();
	}
	/*
	// method for TEST. This method should be removed.
	public void scan() throws IOException, LexicalException {
		Token t;
		do {
			t = nextToken();
			System.out.println(t.toString());
		} while (t != null);
	}
	*/
	public Token consume() throws IOException, LexicalException {
		Token ret;
		if (lookAhead == null) {
			ret = nextToken();
		} else {
			ret = lookAhead;
			lookAhead = null;
		}
		
		return ret;
	}
	
	public Token peek() throws IOException, LexicalException {
		if (lookAhead == null) {
			lookAhead = nextToken();
		}
		
		return lookAhead;
	}
	
	private Token nextToken() throws IOException, LexicalException {
		Token t = null;
		
		// skip white space characters
		while (Character.isWhitespace(character)) {
			if (character == '\n') {
				line++;
				position = 0;
			}
			
			read();
		}

		// end of stream reached
		if ((byte)character < 0) {
			throw new EOFException();
		}
		
		// keep the start position of this token
		int tline = line;
		int tposition = position;

		String val = "";
		
		// identifier
		if (Character.isLetter(character)) {
			do {
				val += character;
				read();
			} while (Character.isLetterOrDigit(character));

			if (reservedIdents.containsKey(val)) {
				t = reservedIdents.get(val);
			} else {
				t = Token.Identifier;
			}

			t.setValue(val);
			t.setLocation(tline, tposition);
			return t;
		}
		
		// number
		while (Character.isDigit(character)) {
			val += character;
			read();
		}
		if (val.length() > 0) {
			t = Token.Number;
			t.setValue(val);
			t.setLocation(tline, tposition);			
			return t;
		}
		
		boolean alreadyConsumed = false;
		val += character;
		switch (character) {
		// a complete token
		case '[':
			t = Token.SqrBracketL;
			break;
		case ']':
			t = Token.SqrBracketR;
			break;
		case '(':
			t = Token.ParenL;
			break;
		case ')':
			t = Token.ParenR;
			break;
		case ';':
			t = Token.Semicolon;
			break;
		case ',':
			t = Token.Comma;
			break;
		case '+':
		case '-':
			t = Token.PlusMinus;
			break;
		case '*':
		case '/':
			t = Token.MultDiv;
			break;
		case '=':
		case '#':
			t = Token.RelOpr;
			break;
		case '.':
			t = Token.Period;
			break;
		
		// need to look next character
		case '<':
		case '>':
			t = Token.RelOpr;
			read();
			if (character == '=') {
				val += character;
			} else {
				alreadyConsumed = true;
			}
			break;
		case ':':
			read();
			if (character == '=') {
				t = Token.Assign;
				val += character;
			} else {
				t = Token.Colon;
				alreadyConsumed = true;
			}
			break;
		default:
			throw new LexicalException(line, position, "unkown character '"+character+"'.");
		}

		t.setValue(val);
		t.setLocation(tline, tposition);

		// consume processed character
		if (!alreadyConsumed) {
			read();
		}
		
		return t;
	}
	
	private void read() throws IOException {
		character = (char) input.read();
		position++;
	}
}